contents

1. 클린 아키텍처란?


2. 핵심 설계 원칙

1. 관심사 분리(Separation of Concerns)

2. 의존성 규칙(Dependency Rule)

3. SOLID 원칙


3. 클린 아키텍처 레이어 구조

클린 아키텍처는 4개의 주요 계층으로 구성됩니다. 안쪽에서 바깥쪽으로 갈수록 외부 기술 의존도와 구현 세부사항이 높아집니다.

A. 엔티티(Entities)

B. 유스케이스(Use Cases, 인터랙터)

C. 인터페이스 어댑터(Interface Adapters)

D. 프레임워크와 드라이버(Frameworks & Drivers)


레이어 정리

[외부 프레임워크] <--- [인터페이스 어댑터] <--- [유스케이스] <--- [엔티티]
   (가장 바깥)           (중간 연결)         (비즈니스 룰)    (핵심 도메인)

4. Clean Architecture의 주요 특징


5. 클린 아키텍처 설계 예시

  1. 요청이 들어오면(Controller): 인터페이스 어댑터에서 입력을 받아 DTO/객체로 변환.
  2. 유스케이스(Interactor)가 실행: 엔티티/비즈니스 규칙 객체를 활용해 처리 및 결과 생성.
  3. 결과가 인터페이스 어댑터로 이동: 결과를 프레젠터, 뷰모델 등 필요한 형식으로 변환.
  4. 외부(DB, UI 등)에 전달: 데이터 저장, 화면 출력 등 내부와 외부의 경계를 유지.

6. Clean Architecture 베스트 프랙티스


7. 요약


스프링에서 간단한 클린아키텍처 예제를 만들어 보겠습니다.


프로젝트 구조 예시

src/main/java/com/example/cleanarchitecture/
├── domain/
│   ├── model/
│   │     └── Product.java          // 엔티티
│   └── repository/
│         └── ProductRepository.java // 도메인 저장소 인터페이스
├── application/
│   └── service/
│         └── ProductService.java   // 비즈니스 로직(유스케이스)
├── infrastructure/
│   ├── persistence/
│   │     └── ProductRepositoryImpl.java // JPA 저장소 구현
│   └── web/
│         └── ProductController.java    // REST 컨트롤러
├── CleanArchitectureApplication.java   // Spring Boot 메인 클래스

1. 도메인 계층 – 엔티티 & 저장소

// domain/model/Product.java
public class Product {
    private Long id;
    private String name;
    private double price;

    // 생성자, Getter, Setter 등
}

// domain/repository/ProductRepository.java
import java.util.List;
import java.util.Optional;

public interface ProductRepository {
    Optional<Product> findById(Long id);
    List<Product> findAll();
    Product save(Product product);
}

2. 애플리케이션 계층 – 유스케이스 / 비즈니스 로직

// application/service/ProductService.java
import com.example.cleanarchitecture.domain.model.Product;
import com.example.cleanarchitecture.domain.repository.ProductRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;

@Service
public class ProductService {
    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public Optional<Product> getProductById(Long id) {
        return productRepository.findById(id);
    }

    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    public Product addProduct(Product product) {
        return productRepository.save(product);
    }
}

3. 인프라 계층 – DB 구현

// infrastructure/persistence/ProductRepositoryImpl.java
import com.example.cleanarchitecture.domain.model.Product;
import com.example.cleanarchitecture.domain.repository.ProductRepository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface ProductRepositoryImpl extends JpaRepository<Product, Long>, ProductRepository {
    // JpaRepository가 기본 CRUD 제공
}

4. 웹 계층 – REST 컨트롤러

// infrastructure/web/ProductController.java
import com.example.cleanarchitecture.application.service.ProductService;
import com.example.cleanarchitecture.domain.model.Product;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/products")
public class ProductController {
    private final ProductService productService;
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping
    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
        Optional<Product> product = productService.getProductById(id);
        return product.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }

    @PostMapping
    public ResponseEntity<Product> createProduct(@RequestBody Product product) {
        Product savedProduct = productService.addProduct(product);
        return ResponseEntity.ok(savedProduct);
    }
}

5. Spring Boot 메인 클래스

// CleanArchitectureApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CleanArchitectureApplication {
    public static void main(String[] args) {
        SpringApplication.run(CleanArchitectureApplication.class, args);
    }
}

요약:
각 계층은 자신만의 책임을 가지며 필요한 추상화 및 내부 레이어만 의존합니다.

이러한 계층적 분리는 클린 아키텍처의 핵심 원칙을 실제로 구현한 예제입니다.


솔직히 위의 코드를 봐선 일반적으로 구현하는 API를 리턴하는 MVC와 다른 점이 잘 눈에 보이지 않습니다. 이에 대한 설명을 조금 더 해보겠습니다.


차이점 요약: Clean Architecture vs. 일반 Spring MVC

1. 의존성의 방향

2. 레이어의 책임 범위와 변경에 대한 내구성

3. Use Case (유스케이스)와 Application 서비스의 강조

4. 프레임워크/외부 기술로부터의 독립성


핵심 구조 예시

[Spring Controller, JPA Repository, DTO, DB]         (외부 Infra/Framework Layer)
        ↓                            (인터페이스, 추상화로 연결)
[UseCase (Application Layer)]         ←   (비즈니스 로직과 트랜잭션)
        ↓
[Domain Model (Entity Layer)]          ←   (핵심 규칙/도메인 객체)

결론


핵심 요약:
Spring MVC는 프레임워크 중심 구조, Clean Architecture는 비즈니스 규칙 중심의 의존성 역전과 프레임워크 분리 구조입니다.
이로 인해 테스트/재사용/유지보수/확장성 모두 Clean Architecture가 복잡한 대형, 장기 서비스에 더 유리합니다.

references